package jcu.sal.common.agents.netty;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import jcu.sal.common.CommandFactory;
import jcu.sal.common.Constants;
import jcu.sal.common.StreamID;
import jcu.sal.common.CommandFactory.Command;
import jcu.sal.common.agents.SALAgent;
import jcu.sal.common.events.ClientEventHandler;
import jcu.sal.common.exceptions.ConfigurationException;
import jcu.sal.common.exceptions.NotFoundException;
import jcu.sal.common.exceptions.SALDocumentException;
import jcu.sal.common.exceptions.SensorControlException;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

/**
 * @author Marc Hammerton
 * 
 */
public class NettyClientStub implements SALAgent {

	private ClientBootstrap bootstrap;
	private ChannelFuture cf;
	private Channel channel;
	private NettyClientHandler handler = null;

	private String name;
	private String agentIP;

	public NettyClientStub(String agentIP, String name) {
		this.name = name;
		this.agentIP = agentIP;

		ChannelFactory factory = new NioClientSocketChannelFactory(Executors
				.newCachedThreadPool(), Executors.newCachedThreadPool());

		bootstrap = new ClientBootstrap(factory);
		bootstrap.setPipelineFactory(new NettyClientPipelineFactory());

		cf = bootstrap.connect(new InetSocketAddress(agentIP,
				NettyConstants.PORT));

		channel = cf.getChannel();

		try {
			handler = (NettyClientHandler) cf.await().getChannel()
					.getPipeline().getLast();
		} catch (InterruptedException e) {
			System.out.println("Client Exception:");
			e.printStackTrace();
		}
		connect();
	}

	private void connect() {
		NettyMessage message = new NettyMessage(NettyConstants.REQ_REGISTER);
		message.addData(this.name);

		channel.write(message);
		handler.responseReceived();
	}

	public void release() {
		channel.write(NettyConstants.REQ_UNREGISTER);
		handler.responseReceived();

		channel.disconnect();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#addProtocol(java.lang.String,
	 * boolean)
	 */
	@Override
	public void addProtocol(String xml, boolean loadSensors)
			throws ConfigurationException, SALDocumentException {
		NettyMessage message = new NettyMessage(NettyConstants.REQ_ADD_PROTOCOL);
		message.addData(loadSensors);
		message.addData(xml);
		channel.write(message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#addSensor(java.lang.String)
	 */
	@Override
	public String addSensor(String xml) throws SALDocumentException,
			ConfigurationException {
		NettyMessage message = new NettyMessage(NettyConstants.REQ_ADD_SENSOR);
		message.addData(xml);
		channel.write(message);

		return handler.responseReceived().toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#getCML(java.lang.String)
	 */
	@Override
	public String getCML(String sid) throws NotFoundException {
		NettyMessage message = new NettyMessage(NettyConstants.REQ_GET_CML);
		message.addData(sid);
		channel.write(message);

		return handler.responseReceived().toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#getID()
	 */
	@Override
	public String getID() {
		return Constants.Netty_Agent_ID_Prefix + agentIP;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#getType()
	 */
	@Override
	public String getType() {
		return Constants.Netty_Agent_type;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#listActiveSensors()
	 */
	@Override
	public String listActiveSensors() {
		channel.write(NettyConstants.REQ_LIST_ACTIVE_SENSORS);

		return handler.responseReceived().toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#listProtocols()
	 */
	@Override
	public String listProtocols() {
		channel.write(NettyConstants.REQ_LIST_PROTOCOLS);

		return handler.responseReceived().toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#listSensor(java.lang.String)
	 */
	@Override
	public String listSensor(String sid) throws NotFoundException {
		long time1 = System.nanoTime();
		NettyMessage message = new NettyMessage(NettyConstants.REQ_LIST_SENSOR);
		message.addData(sid);
		channel.write(message);

		String response = handler.responseReceived().toString();
		long time2 = System.nanoTime();
		System.out.println("listSensors: " + (time2 - time1));
		return response;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#listSensors()
	 */
	@Override
	public String listSensors() {
		long time1 = System.nanoTime();
		channel.write(NettyConstants.REQ_LIST_SENSORS);

		String response = handler.responseReceived().toString();
		long time2 = System.nanoTime();
		System.out.println("listSensors: " + (time2 - time1));
		return response;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * jcu.sal.common.agents.SALAgent#registerEventHandler(jcu.sal.common.events
	 * .ClientEventHandler, java.lang.String)
	 */
	@Override
	public void registerEventHandler(ClientEventHandler eh, String producerID)
			throws NotFoundException {
		if (handler.eventHandler == null) {
			handler.eventHandler = eh;
		}

		if (handler.agent == null) {
			handler.agent = this;
		}

		NettyMessage message = new NettyMessage(
				NettyConstants.REQ_REG_EVENTHANDLER);
		message.addData(producerID);
		channel.write(message);
		handler.responseReceived();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#removeProtocol(java.lang.String,
	 * boolean)
	 */
	@Override
	public void removeProtocol(String pid, boolean removeSensors)
			throws NotFoundException {
		NettyMessage message = new NettyMessage(
				NettyConstants.REQ_REMOVE_PROTOCOL);
		message.addData(removeSensors);
		message.addData(pid);
		channel.write(message);
		handler.responseReceived();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#removeSensor(java.lang.String)
	 */
	@Override
	public void removeSensor(String sid) throws NotFoundException {
		NettyMessage message = new NettyMessage(
				NettyConstants.REQ_REMOVE_SENSOR);
		message.addData(sid);
		channel.write(message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * jcu.sal.common.agents.SALAgent#setupStream(jcu.sal.common.CommandFactory
	 * .Command, java.lang.String)
	 */
	@Override
	public StreamID setupStream(Command c, String sid)
			throws NotFoundException, SensorControlException {
		handler.stream_cb = c.getStreamCallBack();
		Command nettyCommand = CommandFactory.getCommand(c, null);
		NettyMessage message = new NettyMessage(NettyConstants.REQ_SETUP_STREAM);
		message.addData(nettyCommand);
		message.addData(sid);
		channel.write(message);

		StreamID id = null;
		Object response = handler.responseReceived();
		if (response instanceof StreamID)
			id = (StreamID) response;
		return id == null ? null : id.setAgentID(getID());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jcu.sal.common.agents.SALAgent#startStream(jcu.sal.common.StreamID)
	 */
	@Override
	public void startStream(StreamID streamId) throws NotFoundException {
		NettyMessage message = new NettyMessage(NettyConstants.REQ_START_STREAM);
		message.addData(streamId);

		handler.startTime = System.nanoTime();
		channel.write(message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * jcu.sal.common.agents.SALAgent#terminateStream(jcu.sal.common.StreamID)
	 */
	@Override
	public void terminateStream(StreamID streamId) throws NotFoundException {
		NettyMessage message = new NettyMessage(
				NettyConstants.REQ_TERMINATE_STREAM);
		message.addData(streamId);
		handler.startTime = 0;
		handler.framesCounter = 0;
		channel.write(message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * jcu.sal.common.agents.SALAgent#unregisterEventHandler(jcu.sal.common.
	 * events.ClientEventHandler, java.lang.String)
	 */
	@Override
	public void unregisterEventHandler(ClientEventHandler eh, String producerID)
			throws NotFoundException {
		NettyMessage message = new NettyMessage(
				NettyConstants.REQ_UNREG_EVENTHANDLER);
		message.addData(producerID);
		channel.write(message);
		handler.responseReceived();
	}
}
